﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Threading;

using VSTalk.Engine.Core.Context;
using VSTalk.Engine.Core.XmppConnection;
using VSTalk.Engine.Core.XmppUtils;
using VSTalk.Model;
using VSTalk.Tools;

using agsXMPP;
using agsXMPP.protocol.client;

using ShowType = VSTalk.Model.ShowType;

namespace VSTalk.Engine.Core.EntitySession
{
    public class InterlocutorContext : DependencyObject, IInterlocutorContext, INotifyPropertyChanged
    {
        private readonly XmppClientConnection _connection;

        private readonly Client _client;

        private IActionWrapper<object, Presence> _onPresence;

        private ISubscription _resourcesUnit;

        private ISubscription _clientContextUnit;

        private IClientContext ClientContext
        {
            get { return AppServices.Get<ISessionModel>().GetClientContext(_client); }
        }

        private static IConnectionManager ConnectionManager
        {
            get { return AppServices.Get<IConnectionManager>(); }
        }

        private static IModelContext ModelContext
        {
            get { return AppServices.Get<IModelContext>(); }
        }

        public Jid Bare { get; private set; }

        public ContactState State
        {
            get
            {
                return !Resources.Any() ? ContactState.Offline : ContactState.Online;
            }
        }

        public ShowType Show
        {
            get
            {
                if (!Resources.Any())
                {
                    return ShowType.NONE;
                }
                return Resources.First().Show;
            }
        }

        public Interlocutor Interlocutor { get; private set; }

        public ObservableCollection<IInterlocutorResource> Resources { get; private set; }

        public InterlocutorContext(Interlocutor interlocutor)
        {
            Interlocutor = interlocutor;
            Bare = XmppIdConverter.Jid(interlocutor);
            Resources = new ObservableCollection<IInterlocutorResource>();

            _client = ModelContext.GetClientByContact(interlocutor);
            _connection = ConnectionManager
                    .GetOrCreateConnection(_client)
                    .Connection;
        }

        public void StartListen()
        {
            Subscribe();
            StartHandling();
        }

        public void StopListen()
        {
            StopHandling();
            Unsubscribe();
        }

        private void Subscribe()
        {
            var dispatcher = Dispatcher.CurrentDispatcher;
            _onPresence = dispatcher.Wrap<object, Presence>(OnPresence);
            _connection.OnPresence += _onPresence.Exec;
        }

        private void StartHandling()
        {
            _resourcesUnit = Resources.HandleCollectionChanges(initialOrAdd:(resource, p) =>
            {
                PropertyChanged.Notify(() => State);
                PropertyChanged.Notify(() => Show);

                p.Join(resource.SubscribeToChange(() => resource.Show, _ => PropertyChanged.Notify(() => Show)));
            }, removed: () =>
            {
                PropertyChanged.Notify(() => State);
                PropertyChanged.Notify(() => Show);
            });

            _clientContextUnit = ClientContext.SubscribeToChange(() => ClientContext.State,
                sender => ClientContext.State == ClientState.Disconnected,
                sender =>
                {
                    foreach (var source in Resources.ToList())
                    {
                        Resources.Remove(source);
                    }
                });
        }

        private void StopHandling()
        {
            _clientContextUnit.Unsubscribe();
            _resourcesUnit.Unsubscribe();
        }


        private void Unsubscribe()
        {
            _connection.OnPresence -= _onPresence.Exec;
        }

        private void OnPresence(object sender, Presence p)
        {
            if (!string.Equals(p.From.Bare, Bare, StringComparison.InvariantCultureIgnoreCase)) return;
            if (p.Type == PresenceType.available)
            {
                var resource = GetOrCreateResource(p.From);
                resource.Show = (ShowType) Enum.Parse(typeof(ShowType), p.Show.ToString());
                resource.Status = p.Status;
            }
            else if (p.Type == PresenceType.unavailable)
            {
                var resource = GetResource(p.From);
                if (resource == null)
                {
                    return;
                }
                Resources.Remove(resource);
            }
        }

        private IInterlocutorResource GetOrCreateResource(Jid jid)
        {
            return GetResource(jid) ?? CreateResource(jid);
        }

        private IInterlocutorResource GetResource(Jid jid)
        {
            return Resources.FirstOrDefault(resource => Equals(resource.Jid, jid));
        }

        private IInterlocutorResource CreateResource(Jid jid)
        {
            var resource = new InterlocutorResource(Interlocutor, jid);
            Resources.Add(resource);
            return resource;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}